using System;
using System.Collections.Generic;
using TestCases.CompareAssert.Core.Comparers.Interfaces;
using TestCases.CompareAssert.Core.Interfaces;
using TestCases.Utils;
using TestCases.Utils.Pairs;

namespace TestCases.CompareAssert.Core.Builders
{
    public class ResultBuilder : IResultBuilder
    {
        public const string EqualsTemplate = "<{LPath}> equals to <{RPath}> [{LValue} == {RValue}].";
        public const string NotApplicableTemplate = "<{LPath}> cannot be compared with <{RPath}>.";
        public const string NotEqualsTemplate = "<{LPath}> not equals to <{RPath}> [{LValue} != {RValue}].";

        private readonly CompareResult _result;
        private object _args;
        private string _message;

        public ResultBuilder(
            IObjectComparer comparer,
            CompareStatus status,
            IPair<IComparerObject, IComparerObject> objects)
        {
            _result = new CompareResult(comparer, status, objects);
            _message = GetDefaultTemplate(_result.Status);
        }

        public ResultBuilder(
            IObjectComparer comparer,
            CompareStatus status,
            IPair<IComparerObject, IComparerObject> objects,
            ICompareResult childResult) 
        {
            _result = new CompareResult(comparer, status, objects, childResult);
            _message = GetDefaultTemplate(_result.Status);
        }
            
        public ResultBuilder(
            IObjectComparer comparer,
            CompareStatus status,
            IPair<IComparerObject, IComparerObject> objects,
            IEnumerable<ICompareResult> childResults)
        {
            _result = new CompareResult(comparer, status, objects, childResults);
            _message = GetDefaultTemplate(_result.Status);
        }
                                
        public IResultBuilder Message(string message)
        {
            _message = message;
            return this;
        }

        public IResultBuilder With(object args)
        {
            _args = args;
            return this;
        }

        public IResultBuilder Status(CompareStatus status)
        {
            _result.Status = status;
            return this;
        }

        public CompareStatus GetStatus()
        {
            return _result.Status;
        }

        public string GetMessage()
        {
            return _message;
        }

        public object GetArgs()
        {
            return _args;
        }

        public IObjectExpression Left
        {
            get { return new ObjectExpression(this, "<{LPath}>"); }
        }

        public IObjectExpression Right
        {
            get { return new ObjectExpression(this, "<{RPath}>"); }
        }

        public IObjectExpression Both
        {
            get { return new ObjectExpression(this, "<{LPath}> and <{RPath}>"); }
        }

        public ICompareResult GetResult()
        {
            _result.Message = FormatMessage(_result, _message, _args);
            return _result;
        }

        public static string GetDefaultTemplate(CompareStatus status)
        {
            switch (status)
            {
                case CompareStatus.Equals: return EqualsTemplate;
                case CompareStatus.NotEquals: return NotEqualsTemplate;
                default: return NotApplicableTemplate;
            }
        }

        public static string FormatMessage(ICompareResult result, string message, object args)
        {
            Dictionary<string, object> dictionary = null;            
            if (args != null)
            {
                dictionary = new Dictionary<string, object>(args.ToDictionary());
            }

            return message.FormatWith(delegate(string name)
            {
                if (name == "LValue") return result.Left.Object.ToValueString();
                if (name == "RValue") return result.Right.Object.ToValueString();
                if (name == "LPath") return result.Left.Path;
                if (name == "RPath") return result.Right.Path;
                if (name == "LType") return result.Left.Object.GetType().ToTypeString();
                if (name == "RType") return result.Right.Object.GetType().ToTypeString();
                if (name == "Status") return result.Status.ToString();

                object value;
                if (dictionary == null || !dictionary.TryGetValue(name, out value))
                {
                    throw new ArgumentException("ResultBuilder format args doesn't contain " + name.ToValueString() + " argument.");
                }
       
                return value == null 
                    || name.EndsWith("Index") 
                    || name.EndsWith("Key") 
                    || name.EndsWith("Value") 
                    || value is Type 
                    || value is Array
                    ? value.ToValueString() 
                    : value.ToString();                                    
            });
        }
    }
}